using SharpGraphT.Event;
using SharpGraphT.Sharp.Sharp.Util;

namespace SharpGraphT.Graph;

public class DefaultListenableGraph<TV, TE> : GraphDelegator<TV, TE>,
    IListenableGraph<TV, TE>, ICloneable where TE : class, new() {
    private static readonly long SerialVersionUid = -1156773351121025002L;

    public DefaultListenableGraph(IGraph<TV, TE> graph) : base(graph) {
        if (graph is IListenableGraph<TV, TE>) {
            throw new ArgumentException("base graph cannot be listenable");
        }
    }

    public override TE AddEdge(TV sourceVertex, TV targetVertex) {
        var e = base.AddEdge(sourceVertex, targetVertex);

        if (e != null) {
            EdgeAdded?.Invoke(this,
                new GraphEdgeChangeEventArgs<TV, TE>(
                    GraphEdgeChangeEventArgs.EdgeAdded, e,
                    sourceVertex, targetVertex, IGraph.DefaultEdgeWeight));
        }

        return e;
    }

    public override bool AddEdge(TV sourceVertex, TV targetVertex, TE e) {
        var added = base.AddEdge(sourceVertex, targetVertex, e);

        if (added) {
            EdgeAdded?.Invoke(this,
                new GraphEdgeChangeEventArgs<TV, TE>(
                    GraphEdgeChangeEventArgs.EdgeAdded, e,
                    sourceVertex, targetVertex, IGraph.DefaultEdgeWeight));
        }

        return added;
    }

    public override TV AddVertex() {
        var v = base.AddVertex();
        Objects.RequireNonDefault(v,
            "Vertex of a default value should not be added");

        if (!Objects.IsDefault(v)) {
            VertexAdded?.Invoke(this,
                new GraphVertexChangeEventArgs<TV>(
                    GraphVertexChangeEventArgs.VertexAdded, v));
        }

        return v;
    }

    public override bool AddVertex(TV v) {
        Objects.RequireNonDefault(v,
            "Vertex of a default value should not be added");

        var modified = base.AddVertex(v);

        if (modified) {
            VertexAdded?.Invoke(this,
                new GraphVertexChangeEventArgs<TV>(
                    GraphVertexChangeEventArgs.VertexAdded, v));
        }

        return modified;
    }

    public object Clone() {
        var g = (DefaultListenableGraph<TV, TE>)MemberwiseClone();
        g.EdgeAdded = null;
        g.EdgeRemoved = null;
        g.VertexAdded = null;
        g.VertexRemoved = null;
        g.EdgeWeightUpdated = null;
        return g;
    }

    public override TE RemoveEdge(TV sourceVertex, TV targetVertex) {
        var e = base.GetEdge(sourceVertex, targetVertex);
        if (e == null) {
            return e;
        }

        var weight = base.GetEdgeWeight(e);
        if (base.RemoveEdge(e)) {
            EdgeRemoved?.Invoke(this,
                new GraphEdgeChangeEventArgs<TV, TE>(
                    GraphEdgeChangeEventArgs.EdgeRemoved, e,
                    sourceVertex, targetVertex, weight));
        }

        return e;
    }

    public override bool RemoveEdge(TE e) {
        var sourceVertex = GetEdgeSource(e);
        var targetVertex = GetEdgeTarget(e);
        var weight = GetEdgeWeight(e);

        var modified = base.RemoveEdge(e);

        if (modified) {
            EdgeRemoved?.Invoke(this,
                new GraphEdgeChangeEventArgs<TV, TE>(
                    GraphEdgeChangeEventArgs.EdgeRemoved, e,
                    sourceVertex, targetVertex, weight));
        }

        return modified;
    }

    public override bool RemoveVertex(TV v) {
        if (!ContainsVertex(v)) {
            return false;
        }

        var touchingEdgesList = EdgesOf(v);

        RemoveAllEdges(touchingEdgesList.ToList());

        base.RemoveVertex(v);

        VertexRemoved?.Invoke(this,
            new GraphVertexChangeEventArgs<TV>(
                GraphVertexChangeEventArgs.VertexRemoved, v));

        return true;
    }

    public override void SetEdgeWeight(TE e, double weight) {
        base.SetEdgeWeight(e, weight);

        var sourceVertex = GetEdgeSource(e);
        var targetVertex = GetEdgeSource(e);

        EdgeWeightUpdated?.Invoke(this,
            new GraphEdgeChangeEventArgs<TV, TE>(
                GraphEdgeChangeEventArgs.EdgeWeightUpdated, e,
                sourceVertex, targetVertex, weight));
    }

    public DefaultListenableGraph(IGraph<TV, TE> graph, Func<TV> vertexSupplier,
        Func<TE> edgeSupplier) : base(graph, vertexSupplier, edgeSupplier) { }

    public event EventHandler<GraphVertexChangeEventArgs<TV>> VertexAdded;

    public event EventHandler<GraphVertexChangeEventArgs<TV>> VertexRemoved;

    public event EventHandler<GraphEdgeChangeEventArgs<TV, TE>> EdgeAdded;

    public event EventHandler<GraphEdgeChangeEventArgs<TV, TE>> EdgeRemoved;

    public event EventHandler<GraphEdgeChangeEventArgs<TV, TE>>
        EdgeWeightUpdated;
}